import { PREVIEW } from 'cc/env';
import { UIDevice } from './UIDevice';
import { UIPackage } from './UIPackage';
import { AssetManager } from 'cc';

interface PackageBundleInfo {
    bundleName: string;
    binName: string;
    depends?: string[];
}

/**
 * 通过AssetsBundle加载Bundle
 */
export class UIPackageManager {
    private static _packageBundleNames: Map<string, PackageBundleInfo> = new Map();
    private static _loadedPackages: Map<string, UIPackage> = new Map();
    private static _loadingPackages: Map<string, Promise<UIPackage>> = new Map();

    public static bind(packageName: string, bundleName: string, binName: string, deps?:string[]): void {
        this._packageBundleNames.set(packageName, { bundleName, binName, depends: deps });
    }

    public static async load(packageName: string): Promise<UIPackage> {
        if (PREVIEW) {
            return this.loadInternal(packageName, []);
        } else {
            return this.loadInternal(packageName, null);
        }
    }

    private static async loadInternal(packageName: string, loadingPath: Array<string>): Promise<UIPackage> {
        // console.log('load >>>>>>>>>>>>>>>>>>>>>> package:', packageName, new Error().stack);
        if (!UIDevice.inited) return null;

        if (!this._packageBundleNames.has(packageName)) {
            return Promise.reject(`No bundle name for package ${packageName}`);
        }

        if (loadingPath) {
            if (loadingPath.indexOf(packageName) != -1) {
                return Promise.reject(`Recursive loading for package ${packageName}, loading:${loadingPath.join(",")}`);
            }
            loadingPath.push(packageName);
        }
        const info = this._packageBundleNames.get(packageName) as PackageBundleInfo;
        await this._preloadDepencds(info.depends, loadingPath);

        if (this._loadedPackages.has(packageName)) {
            return Promise.resolve(this._loadedPackages.get(packageName) as UIPackage);
        }

        if (this._loadingPackages.has(packageName)) {
            return this._loadingPackages.get(packageName) as Promise<UIPackage>;
        }

        let promise: Promise<UIPackage>;
        promise = UIDevice.inst.loadBundle(info.bundleName).then((bundle) => {
            bundle.onReleaseCallback(this.onBundleRelease.bind(this, packageName));
            return this.loadWithBundle(packageName, info.binName, bundle.getBundle());
        });
        this._loadingPackages.set(packageName, promise);
        return promise;
    }

    private static _preloadDepencds(deps: string[], loadingPath: Array<string>): Promise<any> {
        if (!deps || deps.length == 0) {
            return Promise.resolve();
        }
        const loads: Promise<any>[] = [];
        for (const dep of deps) {
            loads.push(this.loadInternal(dep, loadingPath ? loadingPath.slice() : undefined));
        }
        return Promise.all(loads);
    }

    private static loadWithBundle(packageName: string, path: string, bundle: AssetManager.Bundle): Promise<UIPackage> {
        return new Promise<UIPackage>((resolve, reject) => {
            UIPackage.loadPackage(bundle, path, (err: any, pkg: UIPackage) => {
                if (err) {
                    this._loadingPackages.delete(packageName);
                    reject(err);
                    return;
                }

                this.onPackageLoaded(packageName, pkg);
                resolve(pkg);
            });
        });
    }

    private static onPackageLoaded(packageName: string, pkg: UIPackage): void {
        this._loadedPackages.set(packageName, pkg);
        this._loadingPackages.delete(packageName);
    }

    private static onBundleRelease(packageName: string): void {
        console.log('onBundleRelease >>>>>>>>>>> ', packageName);
        if (this._loadedPackages.has(packageName)) {
            const pkg = this._loadedPackages.get(packageName) as UIPackage;
            this._loadedPackages.delete(packageName);
            UIPackage.removePackage(pkg.id);
        }
    }
}
